#include "globals.h"

char *classPathToInclude(char *cp){
	char *r, *p;
	p=r=strdup(cp);
	for(;*p;p++)
		if(*p=='.') *p='/';
	return r;	
}

char *classPathToLinear(char *cp){
	char *r, *p; 
	p=r=strdup(cp);
	for(;*p;p++)
		if(*p=='.') *p='_';
	return r;	
}

char *hashArguments(Variable *v){
	md5_state_t state;
	md5_byte_t digest[16];
	char hex_output[16*2 + 1];

	md5_init(&state);
	
	Variable *var;
	for(var=v;var;var=var->next){
		md5_append(&state, (const md5_byte_t *)var->type, strlen(var->type));
	}
		
	md5_finish(&state, digest);
	
	int di;
	for (di = 0; di < 16; ++di)
	    sprintf(hex_output + di * 2, "%02x", digest[di]);	
	
	return strdup(hex_output);	
}
char *hashExArguments(Expression *exp){
	md5_state_t state;
	md5_byte_t digest[16];
	char hex_output[16*2 + 1];

	md5_init(&state);
	
	Expression *ex;
	for(ex=exp;ex;ex=(Expression*)ex->next){
		md5_append(&state, (const md5_byte_t *)ex->exType, strlen(ex->exType));
	}
		
	md5_finish(&state, digest);
	
	int di;
	for (di = 0; di < 16; ++di)
	    sprintf(hex_output + di * 2, "%02x", digest[di]);	
	
	return strdup(hex_output);	
}

int isUnOperator(int op){
	if(op==PRE_INC_OP ||
		op==PRE_DEC_OP ||
		op==POST_DEC_OP ||
		op==POST_DEC_OP ||
		op==BNOT_OP ||
		op==LNOT_OP)
			return 1;
	else
		return 0;
}

int isBiOperator(int op){
	if(op==PLUS_OP ||
		op==MINUS_OP ||
		op==BAND_OP ||
		op==BOR_OP ||
		op==BXOR_OP ||
		op==MUL_OP ||
		op==DIV_OP ||
		op==MOD_OP ||
		op==LOW_OP ||
		op==HIG_OP ||
		op==BOR_OP ||
		op==ASSIGN_OP ||
		op==RIGHT_ASSIGN ||
		op==LEFT_ASSIGN ||
		op==ADD_ASSIGN ||
		op==SUB_ASSIGN ||
		op==MUL_ASSIGN ||
		op==DIV_ASSIGN ||
		op==MOD_ASSIGN ||
		op==AND_ASSIGN ||
		op==XOR_ASSIGN ||
		op==OR_ASSIGN ||
		op==RIGHT_OP ||
		op==LEFT_OP ||
		op==AND_OP ||
		op==OR_OP ||
		op==LE_OP ||
		op==GE_OP ||
		op==EQ_OP ||
		op==NE_OP	
		)	return 1;
		else return 0;
}

char *opToStr(int op){
	switch(op){
		case PRE_INC_OP: 	return "++";
		case PRE_DEC_OP: 	return "--";
		case POST_INC_OP:	return "++";
		case POST_DEC_OP:	return "--";	
		case BAND_OP:		return "&";
		case BNOT_OP:		return "~";
		case PLUS_OP:		return "+";
		case MINUS_OP:		return "-";
		case LNOT_OP:		return "!";
		case MUL_OP:		return "*";
		case DIV_OP:		return "/";
		case MOD_OP:		return "%";
		case POINTER_OP:	return "*";
		case LOW_OP:		return "<";
		case HIG_OP:		return ">";
		case BXOR_OP:		return "^";
		case BOR_OP:		return "|";
		case ASSIGN_OP:		return "=";
		case RIGHT_ASSIGN:	return ">>=";
		case LEFT_ASSIGN:	return "<<=";
		case ELLIPSIS:		return "...";
		case ADD_ASSIGN:	return "+=";
		case SUB_ASSIGN:	return "-=";
		case MUL_ASSIGN:	return "*=";
		case DIV_ASSIGN:	return "/=";
		case MOD_ASSIGN:	return "%=";
		case AND_ASSIGN:	return "&=";
		case XOR_ASSIGN:	return "^=";
		case OR_ASSIGN:		return "|=";
		case RIGHT_OP:		return ">>";
		case LEFT_OP:		return "<<";
		case AND_OP:		return "&&";
		case OR_OP:			return "||";
		case LE_OP:			return "<=";
		case GE_OP:			return ">=";
		case EQ_OP:			return "==";
		case NE_OP:			return "!=";		
		default: 			return "";
	}	
}

void generateExpCode(FILE *stream, Class *class, Method *met, Expression *exp){
	char *name, *temp, *type; 
	Method *m;
	int i;
	
	if(exp==0) return;
	
	switch(exp->operator){ 
		case IDENT: name=(char*)exp->left;
//					printf("/*'%s'*/", name);
					i=isSetVariable(name, class->vars);
					if(i){
						exp->exType=strdup(getType(getVariable(name, class->vars)->type));
						if(i>0){
							type=getType(getVariable(name, class->vars)->type);
							fprintf(stream, "(*((%s*)getVariable(this, \"%s\")))", type, name);
						}	
						if(i<0){
							fprintf(stream, "((Class*)getVariable(this, \"%s\"))", name);
						}
						break;	
					}
					
//					printf("//\t'%s' %p\n", name, met->vars);
					i=isSetVariable(name, met->vars);
					if(i){	
						exp->exType=strdup(getVariable(name, met->vars)->type);			
						fputs(name, stream);
						break;
					}
					else{
						error("Undefinded variable.");
						return;
					}
					
		case FUNC_OP:	name=(char*)exp->left;
						
						
						if(exp->right){
							generateExpCode(stdin, class, met, exp->right);
							temp=hashExArguments(exp->right);
							m=getMethod(class->methods, name, temp);
//							if(m==0) {										//temporarly - for class_op testing
//								error("Method not found!");
//								return;
//							}
							
							exp->exType=strdup(m->type);
							
							fprintf(stream, "%s_%s_%s", classPathToLinear(class->fullname), m->name, m->argshs);
							fputs("(this, ", stream);
							generateExpCode(stream, class, met, exp->right);
							fputs(")", stream);
						}
						else {
							temp=hashArguments(0);
							m=getMethod(class->methods, name, temp);
							if(m==0) {
								error("Method not found!");
								return;
							}
							exp->exType=strdup(m->type);
							fprintf(stream, "%s_%s_%s(this)", classPathToLinear(class->fullname), m->name, m->argshs);
						}
						break;
						
		case CLASS_OP:	printf("CLASS_OP[%u]: %u\n",FUNC_OP, exp->right->type);	//thinking end...
						generateExpCode(stdin, class, met, exp->left);
						//generateExpCode(stdin, class, met, exp->right);
						if(exp->right->operator==FUNC_OP){
							name=(char*)exp->right->left;
							type=getVariable(name, class->vars)->type;	//thinking end!!!!!!!
							fprintf(stream, "%s_%s(", "__pakiet__", name);
							generateExpCode(stream, class, met, exp->left);
							if(exp->right->right){
								fputs(", ", stream);
								generateExpCode(stream, class, met, exp->right->right);
							}
							fputs(")", stream);							
						}
						else{
							generateExpCode(stream, class, met, exp->left);
							fputs("=>", stream);
							generateExpCode(stdin, class, met, exp->right);
							
						}
						break;

		case ARRAY_OP:	generateExpCode(stream, class, met, exp->left);
//						printf("//\tarray_left: %p\n", exp->left->exType);
//						printf("//\tarray_left_type: '%s'\n", exp->left->exType);
						if(exp->left->exType[0]!='*' && exp->left->exType[0]!='|') {
							error("This varriable is not an array or pointer!");
							return;
						}
						
						fputs("[", stream);
						generateExpCode(stream, class, met, exp->right);
						fputs("]", stream);
						break; 
								
		case CONSTANT: fputs((void*)exp->left, stream); break;
		case STRING_LITERAL: exp->exType=strdup("*char"); fputs((void*)exp->left, stream); break;

		default:	
					if(isBiOperator(exp->operator)){	
						exp->exType=exp->left->exType;
						generateExpCode(stream, class, met, exp->left);
						fputs(opToStr(exp->operator), stream);
						generateExpCode(stream, class, met, exp->right);
					}
					else if(isUnOperator(exp->operator)){
						exp->exType=exp->left->exType;
						if(exp->operator==POST_INC_OP || exp->operator==POST_DEC_OP ){
							generateExpCode(stream, class, met, exp->left);
							fputs(opToStr(exp->operator), stream);
						}	
						else {
							fputs(opToStr(exp->operator), stream);			
							generateExpCode(stream, class, met, exp->left);				
						}
					}
					else {
						exp->exType=exp->left->exType;			
						fprintf(stream, "/*Operator #%u*/", exp->operator);
					}	 
				
				/*error("Undefinded operator.");*/	
	}
}

void generateElemntsCode(FILE *stream, Class *class, Method *met, Element *elem){
//	char *cname=classPathToLinear(class->fullname);
	unsigned count=0;
	
	Element *el;
	BaseElement *bel;
	Expression *exp;
	Variable *var;
	char *ttype;
	
	for(el=elem;el;el=el->next){
		fprintf(stream, "\t//Element: %u of type %u\n", count++, el->type);
		
		switch(el->type){
			case CODE_BLOCK:	fputs("{\n", stream);
								//generateElemntsCode(stream, cname, el->content);
								fputs("}\n", stream);
								break;
								
			case DECLARATION:	bel=BEL(el);
								if(met->vars)
									setLastVariable(met->vars, VAR(bel->content));
								else
									met->vars=VAR(bel->content);
								ttype=translateType(VAR(bel->content)->type);
								fprintf(stream, "\t%s", ttype);
								for(var=bel->content;var;var=var->next){
									fprintf(stream, " %s%c", var->name, var->next?',':' ');
								}
								fputs(";\n", stream);
								break;
			case EXP:			exp=EX(el);
								generateExpCode(stream, class, met, exp);
								fputs(";\n", stream);					
								break;
			default:			fprintf(stream, "//This should be something of type=%u\n", el->type);
		
		}
				
	}
}


void generateConstructor(FILE *stream, Class *class, Method *met){
	char *cname=classPathToLinear(class->fullname);
	fprintf(stream, "\nvoid %s_%s_%s(Class *this", cname, met->name, met->argshs);
	
	Variable *v;
	for(v=met->args;v;v=v->next){
		fprintf(stream, ", %s %s", translateType(v->type),v->name);
	}
	
	fputs("){\n", stream);
	generateElemntsCode(stream, class, met, met->code);
	fputs("}\n", stream);
}

void generateMethod(FILE *stream, Class *class, Method *met){
	char *cname=classPathToLinear(class->fullname);
	fprintf(stream, "\n%s %s_%s_%s(Class *this", translateType(met->type), cname, met->name, met->argshs);
	
	Variable *v;
	for(v=met->args;v;v=v->next){
		fprintf(stream, ", %s %s", translateType(v->type), v->name);
	}
	
	fputs("){\n", stream);
	generateElemntsCode(stream, class, met, met->code);
	fputs("}\n", stream);
}

void generateCode(FILE *stream){
	fprintf(stream, "/***BodyLanguage C output. Generated from %s***/\n\n", __filename);	
	fprintf(stream, "//package: %s\n", program->class->package);
	fputs("#include <__buildin.h>\n", stream);
	
	Import *imp;
	for(imp=program->imports;imp;imp=imp->next){
		fprintf(stream, "#include <%s.h>\n", classPathToInclude(imp->fullname));
	}
	
	Class *class=program->class; 
	char *cname=classPathToLinear(class->fullname);
	fprintf(stream, "\n\nClass %s_class=NEW(Class);\n", cname);
	
	fprintf(stream, "\nvoid %s_new(Class *this){\n", cname);
	
//	printf(">>>%u<<<\n", class->vars);
//	return;
	
	Variable *var;
	for(var=class->vars;var;var=var->next){
		fprintf(stream, "\taddVariable(this, \"%s\", \"%s\");\n", translateType(var->type), var->name);
	}
	fputs("\n", stream);
	 
	Method *met;
//	unsigned count=0;
//	for(met=class->constructors;met;met=met->next){
//		met->argshs=hashArguments(met->args);
//		fprintf(stream, "\taddConstructor(this, \"%s\", \"%s\", %s_%s_%u);\n", met->name, met->argshs, cname, met->name, count++);		
//	} 
//	
//	fputs("\n", stream);
//	
//	count=0;
//	for(met=class->methods;met;met=met->next){
//		met->argshs=hashArguments(met->args);
//		fprintf(stream, "\taddMethod(this, \"%s\", \"%s\",  %s_%s_%u);\n", met->name, met->argshs, cname, met->name, count++);		
//	}
//	
	fputs("}\n", stream); 
//	
//	count=0;
	for(met=class->constructors;met;met=met->next){
		generateConstructor(stream, class, met);
	}
	
	fputs("\n", stream); 
	
//	count=0;
	for(met=class->methods;met;met=met->next){
		generateMethod(stream, class, met);
	}
	
	fputs("\n/***EOF***/\n", stream);
}

